Micron Document
πŸŽ–οΈGitΠ―Ρ€Π°πŸŽ–οΈ


Displaying Rendered β€’ View raw β€’ Download

specs/006-kmp-project-structure/spec.md 988db081b289cbc23d9bceef8e7d85ab46d0e6a4 (988db081) Text, 22.88 KB

Feature Specification: KMP Recommended Project Structure Alignment

Feature Branch: T383838020-kmp-project-structure
Created: 2025-07-15
Status: Draft
Input: User description: "Restructure Meshtastic Android project to align with the updated Kotlin Multiplatform recommended project structure"
Cross-Platform Spec: N/A β€” this is a build/infrastructure change with no user-facing behavior changes across platforms

Summary

The Meshtastic Android project should be aligned with the official Kotlin Multiplatform recommended project structure as documented in the KMP project structure guide and the JetBrains blog post on the new KMP default structure. The project already runs on AGP 9.2.1 and already applies the T383838com.android.kotlin.multiplatform.library plugin via convention plugins, but an audit reveals that all 27 KMP modules still contain legacy T383838android {} blocks inside T383838kotlin {} that should be migrated to the recommended T383838kotlin.androidLibrary {} top-level DSL. This effort focuses on completing that migration, validating module boundaries, and ensuring convention plugins enforce the canonical patterns β€” so the project fully conforms to the new structure with clear module responsibilities.

Goals

1. Migrate all legacy T383838android {} blocks inside T383838kotlin {} to the recommended T383838kotlin.androidLibrary {} top-level DSL across all 27 KMP modules in T383838core/ and T383838feature/.
2. Validate and harden convention plugins so that T383838KmpLibraryConventionPlugin and T383838KmpFeatureConventionPlugin configure Android targets exclusively through the new plugin's DSL, preventing legacy patterns from being reintroduced.
3. Confirm entry-point module separation β€” T383838app/ (Android) and T383838desktop/ (JVM) are already separate entry-point modules with no shared business logic, satisfying the AGP 9 mandatory requirement.
4. Document the module boundary model β€” map the current T383838core/ (shared logic) and T383838feature/ (shared UI + logic) organization to the recommended T383838sharedLogic / T383838sharedUI categories, confirming they already satisfy the recommended split.
5. Ensure forward compatibility β€” verify the project structure is compatible with future KMP toolchain updates and potential new targets (iOS, web) without requiring another structural overhaul.

Non-Goals

β€’ Introducing iOS, web, or server modules β€” while the recommended structure supports these, this spec scopes only the existing Android + Desktop targets.
β€’ Renaming modules to match the default template names β€” the recommended structure uses T383838shared, T383838androidApp, T383838desktopApp as defaults, but the project's existing T383838core/, T383838feature/, T383838app/, T383838desktop/ naming is equally valid and will not be renamed for cosmetic alignment.
β€’ Changing application behavior or UI β€” this is purely a build infrastructure and module organization change. No user-facing functionality changes.
β€’ Migrating away from existing technology choices β€” Koin, Ktor, Room KMP, Compose Multiplatform, and Navigation 3 remain as-is per the constitution.
β€’ Migrating T383838feature:widget away from T383838com.android.library β€” this module is genuinely Android-only (Glance app widgets) and correctly uses the Android library plugin, not the KMP library plugin.

User Scenarios & Testing (mandatory)

User Story 1 - Developer Builds Successfully After Restructuring (Priority: P1)

As a contributor, I want the project to build successfully on both Android and Desktop targets after any structural changes, so that my development workflow is uninterrupted.

Why this priority: A broken build blocks all development. This is the absolute minimum bar for any structural change.

Independent Test: Can be fully tested by running T383838./gradlew assembleDebug, T383838./gradlew :desktop:packageUberJarForCurrentOS, and T383838./gradlew allTests and verifying all pass.

Acceptance Scenarios:

1. Given the restructured project, When a developer runs the full Android build (T383838assembleDebug), Then the build completes with zero errors.
2. Given the restructured project, When a developer runs the Desktop build (T383838:desktop:packageUberJarForCurrentOS), Then the build completes with zero errors.
3. Given the restructured project, When a developer runs all tests (T383838allTests), Then all existing tests pass with no regressions.
4. Given the restructured project with T383838DESKTOP_ONLY=true, When a developer builds without Android SDK, Then the desktop-only build succeeds as before.


User Story 2 - Convention Plugins Reflect Recommended Patterns (Priority: P2)

As a build maintainer, I want the convention plugins in T383838build-logic/ to apply the correct KMP plugin configuration patterns recommended by JetBrains, so that adding new modules follows a clear, documented convention.

Why this priority: Convention plugins are the enforcement layer for project structure. If they are correct, individual module configurations stay consistent.

Independent Test: Can be tested by verifying that T383838KmpLibraryConventionPlugin applies the T383838com.android.kotlin.multiplatform.library plugin and that no KMP library module uses the legacy T383838com.android.library plugin.

Acceptance Scenarios:

1. Given the T383838meshtastic.kmp.library convention plugin, When applied to a KMP module, Then it configures the module using T383838com.android.kotlin.multiplatform.library (not T383838com.android.library).
2. Given the T383838meshtastic.kmp.feature convention plugin, When applied to a feature module, Then it inherits the correct KMP library plugin chain without legacy Android library configuration.
3. Given a new core module is created, When a developer applies T383838meshtastic.kmp.library, Then the module follows the recommended structure with T383838kotlin.androidLibrary {} configuration.


User Story 3 - Module Boundary Clarity for New Contributors (Priority: P3)

As a new contributor, I want a clear separation between entry-point modules, shared business logic modules, and shared UI modules, so that I know where to add new code based on its purpose.

Why this priority: Clear boundaries reduce onboarding friction and prevent architectural drift over time.

Independent Test: Can be tested by reviewing the module dependency graph and verifying that entry-point modules (T383838app/, T383838desktop/) do not contain shared logic, and that T383838core/ modules do not depend on T383838feature/ modules.

Acceptance Scenarios:

1. Given the module dependency graph, When analyzing T383838app/ dependencies, Then T383838app/ depends on T383838core/ and T383838feature/ modules but contains no shared business logic.
2. Given the module dependency graph, When analyzing T383838desktop/ dependencies, Then T383838desktop/ depends on T383838core/ and T383838feature/ modules but contains no shared business logic.
3. Given any T383838core/ module, When examining its dependencies, Then it does not depend on any T383838feature/ module (unidirectional flow preserved).


User Story 4 - Legacy DSL Block Migration (Priority: P1)

As a build maintainer, I want all 27 KMP modules' legacy T383838android {} blocks inside T383838kotlin {} migrated to the recommended T383838kotlin.androidLibrary {} top-level DSL, so that the build configuration fully uses the new plugin's canonical API.

Why this priority: The project already runs AGP 9.2.1 and applies the T383838com.android.kotlin.multiplatform.library plugin, but every KMP module still configures its Android target using the legacy T383838android {} block inside T383838kotlin {} (for namespace, resource settings, etc.). This is the primary gap between the current state and full alignment with the recommended structure. It ties with P1 because the build already works β€” this is about eliminating technical debt before it becomes a blocker.

Independent Test: Can be tested by searching all T383838core/ and T383838feature/ KMP module T383838build.gradle.kts files for T383838android { blocks inside T383838kotlin {} β€” zero should remain after migration. All configuration should appear in T383838kotlin.androidLibrary {} top-level blocks or be handled by convention plugins.

Acceptance Scenarios:

1. Given any KMP library module in T383838core/ or T383838feature/, When examining its T383838build.gradle.kts, Then it contains no T383838android {} block inside T383838kotlin {} β€” Android configuration uses the T383838kotlin.androidLibrary {} top-level DSL or is delegated to convention plugins.
2. Given the T383838configureKotlinMultiplatform() helper in build-logic, When it configures Android targets, Then it uses T383838KotlinMultiplatformAndroidLibraryTarget APIs from the new plugin, not legacy T383838android {} extension configuration.
3. Given the full project with all 27 modules migrated, When building Android and Desktop targets, Then all builds succeed with zero errors.


Edge Cases

β€’ What happens when the T383838DESKTOP_ONLY mode is active and no Android SDK is available? The restructured plugins must continue to conditionally skip Android plugin application (as the current T383838isDesktopOnly guard already does).
β€’ How does the system handle modules that are genuinely Android-only (e.g., T383838core:api, T383838core:barcode, T383838screenshot-tests)? These modules should continue using T383838com.android.library or T383838com.android.application as appropriate β€” the KMP library plugin applies only to multiplatform modules.
β€’ What happens if a module currently uses T383838com.android.library but should be KMP? The migration path must be documented and executed per-module, with build verification at each step.

Architecture

Gap Analysis: Current State vs Recommended Structure

The audit reveals the project is substantially aligned with the recommended KMP structure, with one significant gap remaining:

T282828
AREA STATUS FINDING
==== ====== =======

Entry-point separation βœ… DONE app/ (Android) and desktop/ (JVM) are
already separate entry-point modules.
AGP 9 mandatory requirement satisfied.

KMP library plugin adoption βœ… DONE KmpLibraryConventionPlugin already applies
com.android.kotlin.multiplatform.library
via the android-kotlin-multiplatform-library
catalog alias. No androidTarget {} calls.

AGP version βœ… DONE Project already runs AGP 9.2.1.

Legacy android {} blocks in kotlin {} ⚠️ GAP All 27 KMP modules still configure Android
via android {} blocks INSIDE kotlin {} for
namespace, androidResources, withHostTest.
These should migrate to kotlin.androidLibrary {}
top-level DSL blocks.

Module boundary model βœ… DONE core/ = shared business logic (β‰ˆ sharedLogic)
feature/ = shared UI + logic (β‰ˆ sharedUI)
Unidirectional: app/desktop β†’ feature β†’ core

Dependency direction βœ… DONE No reverse dependencies. core/ does not
depend on feature/. Unidirectional flow.

DESKTOP_ONLY mode βœ… DONE Conditional Android plugin skipping via
isDesktopOnly guard in convention plugins.

Android-only modules βœ… DONE feature:widget uses com.android.library
correctly. core:api, core:barcode also
Android-only with appropriate plugins.

Convention plugin architecture ⚠️ GAP configureKotlinMultiplatform() helper
may contain legacy android {} configuration
patterns alongside the new plugin's API.
Needs audit and cleanup.

Affected Modules (Legacy T383838android {} blocks to migrate)

Core modules (19):
T383838core:ble, T383838core:common, T383838core:data, T383838core:database, T383838core:datastore, T383838core:di, T383838core:domain, T383838core:model, T383838core:navigation, T383838core:network, T383838core:nfc, T383838core:prefs, T383838core:proto, T383838core:repository, T383838core:resources, T383838core:service, T383838core:takserver, T383838core:testing, T383838core:ui

Feature modules (8 β€” excluding widget):
T383838feature:connections, T383838feature:firmware, T383838feature:intro, T383838feature:map, T383838feature:messaging, T383838feature:node, T383838feature:settings, T383838feature:wifi-provision

Not affected (correctly using T383838com.android.library or T383838com.android.application):
T383838app/, T383838desktop/, T383838feature:widget, T383838core:api, T383838core:barcode, T383838screenshot-tests

Mapping to Recommended Structure

T282828
RECOMMENDED STRUCTURE MESHTASTIC EQUIVALENT NOTES
===================== ======================= =====

androidApp/ app/ βœ… Same role, different name
- kotlin.android (naming is cosmetic)
+ com.android.application

desktopApp/ desktop/ βœ… Same role, different name
- kotlin.jvm + compose

shared/ (single shared mod) core/ + feature/ βœ… Already modularized further
OR (27+ KMP modules) than the default; this is the
sharedLogic/ + sharedUI/ core/ β‰ˆ sharedLogic recommended "advanced" pattern
feature/ β‰ˆ sharedUI

build-logic/ build-logic/ βœ… Project-specific convention
plugins (not prescribed by
JetBrains but aligned)

Reference Projects Comparison

The JetBrains blog cites T383838kotlinconf-app, T383838KMP-App-Template, and T383838RSS Reader as reference implementations. The Meshtastic project is more mature and more modularized than any of these, having already decomposed the monolithic T383838shared module into granular T383838core/ and T383838feature/ modules. This is the recommended evolution path for larger projects.

Key Components

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Component β”‚ Mo… β”‚ Purpose β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ KmpLibraryConventionPlugin β”‚ T383838bu… β”‚ Applies KMP + Android library plugins to shared modules β”‚
β”‚ KmpFeatureConventionPlugin β”‚ T383838bu… β”‚ Composite plugin for feature modules (KMP + Compose + Ko… β”‚
β”‚ KmpJvmAndroidConventionPlugin β”‚ T383838bu… β”‚ Configures jvmAndroidMain shared source set β”‚
β”‚ AndroidLibraryConventionPlugin β”‚ T383838bu… β”‚ Legacy Android library plugin (for Android-only modules) β”‚
β”‚ configureKotlinMultiplatform() β”‚ T383838bu… β”‚ Shared Kotlin/Android configuration helper β”‚
β”‚ settings.gradle.kts β”‚ Ro… β”‚ Module registration and plugin management β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Requirements (mandatory)

Functional Requirements

β€’ FR-001: All 27 KMP library modules (T383838core/* except T383838core:api and T383838core:barcode, plus T383838feature/* except T383838feature:widget) MUST have their T383838android {} blocks inside T383838kotlin {} migrated to T383838kotlin.androidLibrary {} top-level DSL blocks.
β€’ FR-002: Entry-point modules (T383838app/, T383838desktop/) MUST remain as standalone application modules that depend on shared modules but contain no reusable business logic. This is already satisfied and MUST NOT regress.
β€’ FR-003: The T383838KmpLibraryConventionPlugin MUST configure Android target properties (namespace, compileSdk, minSdk, resource settings) exclusively through the T383838com.android.kotlin.multiplatform.library plugin's API (T383838KotlinMultiplatformAndroidLibraryTarget), not through legacy T383838android {} extension blocks.
β€’ FR-004: Android-only modules (T383838core:api, T383838core:barcode, T383838feature:widget, T383838screenshot-tests) MUST continue using T383838com.android.library or T383838com.android.application as they are not multiplatform modules.
β€’ FR-005: The T383838DESKTOP_ONLY build mode MUST continue functioning β€” when active, Android plugin application MUST be skipped for KMP modules (existing T383838isDesktopOnly guard preserved).
β€’ FR-006: Module dependency direction MUST remain unidirectional: T383838app/desktop β†’ feature β†’ core β†’ build-logic. No reverse dependencies.
β€’ FR-007: The T383838configureKotlinMultiplatform() helper function MUST be audited and updated to remove any residual legacy configuration that duplicates or conflicts with the new plugin's target configuration.
β€’ FR-008: Convention plugins MUST prevent future modules from using the legacy T383838android {} pattern β€” applying T383838meshtastic.kmp.library MUST automatically configure the Android target through the new plugin's DSL with no manual T383838android {} block needed in the module's T383838build.gradle.kts.

Non-Functional Requirements

β€’ NFR-001: The restructuring MUST NOT increase full-project build time by more than 5%.
β€’ NFR-002: Gradle configuration cache, isolated projects, and parallel execution (T383838gradle.properties settings) MUST remain functional after changes.
β€’ NFR-003: All changes MUST be backward-compatible within a single migration PR β€” no intermediate broken states on the main branch.
β€’ NFR-004: The migration MUST be documentable as a step-by-step checklist that other Meshtastic platform repositories can reference.

Source-Set Impact

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Source Set β”‚ Im… β”‚ Justification β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ T383838commonMain β”‚ No… β”‚ Business logic and UI remain in commonMain; only build configuration… β”‚
β”‚ T383838androidMain β”‚ No… β”‚ Platform-specific code unchanged; plugin configuration changes only β”‚
β”‚ T383838jvmMain β”‚ No… β”‚ Desktop-specific code unchanged; plugin configuration changes only β”‚
β”‚ Build files β”‚ Mo… β”‚ Plugin IDs and configuration blocks updated to match recommended pat… β”‚
β”‚ Convention plugins β”‚ Mo… β”‚ Updated to enforce recommended plugin application and configuration … β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Privacy Assessment

β€’ [x] No PII, location data, or cryptographic keys logged or exposed
β€’ [x] No new network calls that transmit user data
β€’ [x] Proto submodule (T383838core/proto) not modified (read-only upstream)

This feature is purely a build infrastructure change with no runtime behavior changes.

Success Criteria (mandatory)

Measurable Outcomes

β€’ SC-001: All 27 KMP library modules have zero T383838android {} blocks inside T383838kotlin {} β€” all Android configuration uses T383838kotlin.androidLibrary {} top-level DSL or is handled by convention plugins.
β€’ SC-002: Full project builds succeed on both Android (T383838assembleDebug) and Desktop (T383838:desktop:packageUberJarForCurrentOS) with zero new errors after migration.
β€’ SC-003: All existing tests pass (T383838allTests) with zero regressions after migration.
β€’ SC-004: The T383838DESKTOP_ONLY build mode continues to function correctly, building without Android SDK.
β€’ SC-005: Build time for a clean T383838assembleDebug does not increase by more than 5% compared to the pre-migration baseline.
β€’ SC-006: A new contributor can add a new KMP module by applying one convention plugin (T383838meshtastic.kmp.library) and the module is correctly configured for both Android and Desktop targets with no manual T383838android {} block needed.
β€’ SC-007: The T383838configureKotlinMultiplatform() helper contains zero references to legacy T383838android {} extension configuration β€” all Android target configuration goes through the new plugin's API.

Assumptions

β€’ All business logic and UI composables reside in T383838commonMain source set (per Constitution Β§I, Β§III).
β€’ The project already runs AGP 9.2.1 and applies T383838com.android.kotlin.multiplatform.library in T383838KmpLibraryConventionPlugin β€” the plugin adoption is complete; only the DSL migration from T383838android {} to T383838kotlin.androidLibrary {} remains.
β€’ The T383838configureKotlinMultiplatform() helper already uses T383838KotlinMultiplatformAndroidLibraryTarget for compileSdk/minSdk configuration β€” any remaining legacy T383838android {} blocks are in individual module T383838build.gradle.kts files, not solely in convention plugins.
β€’ No module uses the deprecated T383838androidTarget {} call β€” this was verified by audit (zero matches found).
β€’ The typical T383838android {} block content in KMP modules is limited to T383838namespace and T383838androidResources.enable = false and occasionally T383838withHostTest {} β€” migration should be mechanical.
β€’ Android-only modules (T383838core:api, T383838core:barcode, T383838feature:widget) are not candidates for the KMP library plugin migration β€” they correctly remain on T383838com.android.library.
β€’ The Gradle version catalog (T383838libs.versions.toml) already declares the T383838android-kotlin-multiplatform-library plugin alias pointing to AGP 9.2.1.
β€’ The T383838jvmAndroidMain shared source set pattern (used by some modules via T383838meshtastic.kmp.jvm.android) is compatible with the new plugin and does not conflict with the T383838kotlin.androidLibrary {} DSL.
β€’ The recommended structure's module naming (T383838shared, T383838androidApp, T383838desktopApp) is a default convention, not a requirement β€” the project's existing T383838core/, T383838feature/, T383838app/, T383838desktop/ naming is equally valid per the JetBrains documentation.

Served by rngit 1.5.0 - Generated in 0.06s